home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1996 / MacHack 1996.toast / Hacks / Hacks ’89 / PopUpCtl / PopUp.c next >
Encoding:
Text File  |  1989-03-12  |  12.6 KB  |  424 lines  |  [TEXT/KAHL]

  1. /*
  2.  *    Written by Leonard Rosenthol, Copyright©1988 by LazerWare, inc.
  3.  *    
  4.  *    This is sample source for a CDEF.  In particular it embodies all the Apple Human
  5.  *    Interface Guidelines dealing with popup menus, as well as those of BMUG DevSig.
  6.  *
  7.  *    To use it define or create a control whose contrlRfCon field contains the menu id
  8.  *    of a menu to be used as a popup menu.  If the menu is to be created by the control
  9.  *    it must have a MENU resource definition with a resource id the same as the menu id
  10.  *    field.  If the menu is created by the program before the control is created it must
  11.  *    be inserted into the menu bar with InsertMenu(mh, hierMenu).
  12.  *
  13.  *    If the control has a title it will be displayed to the left of the popup menu.  The
  14.  *    menu's title is ignored.
  15.  *    
  16.  *    If the variant code (low 4 bits of the control def id) is zero the drawn menu will
  17.  *    be left justified in the given rectangle, otherwise it will be centered.
  18.  *
  19.  *    This code was compiled under LSC 3.01p4
  20.  */
  21.  
  22.  
  23. /* predefine some miscellaneous functions */
  24. void        CalcPopUpRect();
  25. MenuHandle    GetMenuHandle();
  26.  
  27. #define        HSeparation    0        /* distance between label and popup box */
  28. #define        HSpace        3        /* horizontal whitespace around popup box */
  29. #define        VSpace        1        /* vertical whitespace around popup box */
  30. #define        NULL        0L        /* Just what you think it is */
  31.  
  32. /* draw the control upon the screen.  Note that there are several possible
  33.     cases here:
  34.     
  35.     contrlVis is 0:            The control isn't visible.  No real reason for us to
  36.                         have gotten here, but in any case, we don't draw anything
  37.     
  38.     param:                    This is nominally the part that needs redrawing.  Since
  39.                         we don't have many parts we just always draw them all.  There
  40.                         is a special case value of 129 which means that the value
  41.                         has been changed and appropriate measures need to be taken.
  42.                         We use this to uncheck the old value and check the new one.
  43.     
  44.     contrlHilite is 255:    The control is inactive.  It get's drawn, just like
  45.                         normal, but then grey'ed out.  
  46. */
  47. void DrawPopUp(varcode, theControl, param)
  48. int varcode;
  49. ControlHandle theControl;
  50. long param;
  51. {
  52.     char        string[256];
  53.     MenuHandle    mh;
  54.     Rect        rect;
  55.     int            numItems, counter, checkWidth = CharWidth(0x12);
  56.     
  57.     /* save our selves a lot of work if the control isn't visible */
  58.     if((**theControl).contrlVis == 0)
  59.         return;
  60.     
  61.     /* get the menu handle since we're going to need it eventually */
  62.     mh = GetMenuHandle(theControl);
  63.     
  64.     /* get the actual bounds of the item, this varies as the length
  65.         of the string displayed does since it looks sick to have this
  66.         big long rectangle with "1" in it */
  67.     CalcPopUpRect(varcode, theControl, &rect);
  68.  
  69.     /* if the control has a title draw it in */
  70.     if(*(**theControl).contrlTitle)
  71.     {
  72.         MoveTo((**theControl).contrlRect.left, rect.bottom - GetFontOffset() - VSpace);
  73.         DrawString((**theControl).contrlTitle);
  74.     }
  75.     
  76.     /* if for some reason the menu hasn't been defined, never fear
  77.         we just draw the title and exit */
  78.     if(mh)
  79.     {
  80.         /* get the currently selected item's string */
  81.         GetItem(mh, (**theControl).contrlValue, string);
  82.         
  83.         /* erase, frame and drop shadow the future menu item */
  84.         EraseRect(&rect);
  85.         FrameRect(&rect);
  86.         MoveTo(rect.left + 1, rect.bottom);
  87.         LineTo(rect.right, rect.bottom);
  88.         LineTo(rect.right, rect.top + 1);
  89.     
  90.         /* draw the actual menu item itself */
  91.         /* we now leave room for the checkMark! */
  92.         MoveTo(rect.left + /*HSpace+*/ checkWidth, rect.bottom - GetFontOffset() - VSpace);
  93.         DrawString(string);
  94.     
  95.         /* Reset the checkMark to the currently selected item! */
  96.             
  97.             numItems = CountMItems(mh);    
  98.             for (counter=1; counter<=numItems;counter++)
  99.                 CheckItem(mh, counter, FALSE);    /* Turn them all off!! */
  100.             
  101.             CheckItem(mh, (**theControl).contrlValue, TRUE);    /* Turn on the one! */
  102.     }
  103.     
  104.     /* the control is inactive so grey it out */
  105.     if((**theControl).contrlHilite == 255)
  106.     {
  107.         PenState        ps;
  108.         long            gray[2];
  109.         
  110.         /* save away the old pen parameters */
  111.         GetPenState(&ps);
  112.         
  113.         /* good pattern for clearing bits */
  114.         PenMode(patBic);
  115.         
  116.         /* 50% grey pattern so's we don't have to rely on finding one
  117.             somewhere */
  118.         gray[0] = 0xaa55aa55;
  119.         gray[1] = 0xaa55aa55;
  120.         PenPat(gray);
  121.         
  122.         /* grey out the whole control, including the title */
  123.         rect.left = (**theControl).contrlRect.left;
  124.         InsetRect(&rect, -1, -1);
  125.         PaintRect(&rect);
  126.         
  127.         /* restore the saved parameters */
  128.         SetPenState(&ps);
  129.     }
  130. }
  131.  
  132. /* check to see if a click is in our control */
  133. HitPopUp(varcode, theControl, param)
  134. int                varcode;
  135. ControlHandle    theControl;
  136. Point            param;
  137. {
  138.     Rect        rect;
  139.     
  140.     /* if the control isn't visible click obviously isn't ours */
  141.     if((**theControl).contrlVis)
  142.     {
  143.         /* find out portion of the dialog the popup portion actually
  144.             occupies and check to see if the mouse is in it */
  145.         CalcPopUpRect(varcode, theControl, &rect);
  146.         if(PtInRect(param, &rect))
  147.         {
  148.             /* if so return like a button so TrackControl will
  149.                 be called as appropriate */
  150.             return inButton;
  151.         }
  152.         else
  153.             return 0;
  154.     }
  155.         
  156.     return 0;
  157. }
  158.  
  159. /* compute a region which looks like our control.  Actually pretty
  160.     easy, it's a rectangle the size of the control */
  161. void CalcPopUp(varcode, theControl, param)
  162. int                varcode;
  163. ControlHandle    theControl;
  164. RgnHandle        param;
  165. {
  166.     Rect        rect;
  167.  
  168.     CalcPopUpRect(varcode, theControl, &rect);
  169.     
  170.     OpenRgn();
  171.     FrameRect(&(**theControl).contrlRect);
  172.     CloseRgn(param);
  173. }
  174.  
  175. /* called when the control is first created, it has two major
  176.     objectives, stow the MenuHandle away in the data and create
  177.     the menu if necessary. */
  178. void NewPopUp(varcode, theControl, param)
  179. int                varcode;
  180. ControlHandle    theControl;
  181. long            param;
  182. {
  183.     MenuHandle    mh;
  184.     Rect        rect, myRect;
  185.     int            myType;
  186.     Handle        myHandle;
  187.     
  188.     /* see if the menu has already been created by somebody,
  189.         the menu id (must be same as resource id) is in the
  190.         refcon field */
  191.     if((mh = GetMHandle((long) (**theControl).contrlRfCon)) == 0)
  192.     {
  193.         /* nope, the menu doesn't already exist so create it.  If
  194.             that doesn't work we just don't display a menu */
  195.         if((mh = GetMenu((long) (**theControl).contrlRfCon)) == 0)
  196.             mh = NULL;
  197.         else
  198.             /* put the newly created menu in the menu bar as a
  199.                 hierarchical menu so future GetMHandle's will
  200.                 find it */
  201.             InsertMenu(mh, hierMenu);
  202.     }
  203.     
  204.     /* stow the MenuHandle away in the data field like we
  205.         promised we would */
  206.     (**theControl).contrlData = (Handle) mh;
  207.     
  208.     /* we handle our own control action, so when we get clicked
  209.         let us know */
  210.     (**theControl).contrlAction = (ProcPtr) -1;
  211.     
  212.     if(mh)
  213.     {
  214.         (**theControl).contrlMin = 1;
  215.         
  216.         /* the maximum control value is the number of items in
  217.             the menu */
  218.         (**theControl).contrlMax = CountMItems(mh);
  219.         
  220.         /* make sure the control value is within bounds */
  221.         SetCtlValue(theControl, (**theControl).contrlValue);
  222.         
  223.         /* set a check mark on the appropriate item in the menu */
  224.         CheckItem(mh, (**theControl).contrlValue, TRUE);
  225.  
  226.         /* need to set the rect to the maxSize of the menu! */
  227.         CalcPopUpRect(varcode, theControl, &rect);
  228.         (**theControl).contrlRect = rect;
  229.     }
  230. }
  231.  
  232. /* The control has been destroyed, destroy the menu we created also */
  233. void DispPopUp(varcode, theControl, param)
  234. int                varcode;
  235. ControlHandle    theControl;
  236. long            param;
  237. {
  238.     MenuHandle    mh;
  239.     
  240.     if((mh = GetMenuHandle(theControl)) != NULL)
  241.     {
  242.         DeleteMenu((**theControl).contrlRfCon);
  243.         DisposeMenu(mh);
  244.     }
  245. }
  246.  
  247. /* the control has been clicked in and needs to be dragged,
  248.     the special case here is that if param is 0 the control
  249.     manager wants the whole control dragged (for instance
  250.     by ResEdit) and we don't muck with that, just let the
  251.     control manager handle it */
  252. DragPopUp(varcode, theControl, param)
  253. int                varcode;
  254. ControlHandle    theControl;
  255. long            param;
  256. {
  257.     long        menu;
  258.     Rect        rect, invert;
  259.     MenuHandle    mh;
  260.  
  261.     if(!param)                /* if drag whole control, let control manager do it */
  262.     {
  263.         return 0;
  264.     }
  265.     
  266.     /* get the menu handle for our menu.  If we don't have a menu
  267.         there's not much else we can do */
  268.     if(mh = GetMenuHandle(theControl))
  269.     {
  270.         /* find out where our control is on the screen */
  271.         CalcPopUpRect(varcode, theControl, &rect);
  272.         
  273.         /* invert the title area like the user interface
  274.             guidelines say we should */
  275.         SetRect(&invert, (**theControl).contrlRect.left, rect.top, rect.left, rect.bottom);
  276.         if(*(**theControl).contrlTitle)
  277.             InvertRect(&invert);
  278.     
  279.         /* convert the coordinates because popupmenuselect wants them in
  280.             global and we've so far got them in local.  Note that we align
  281.             the top left of the currently selected item with the top left
  282.             of where it's currently drawn so that (like the user interface
  283.             demands) the user doesn't get to change things by surprise. */
  284.         LocalToGlobal((Point *) &rect);
  285.         
  286.         menu = PopUpMenuSelect(mh, rect.top, rect.left, (**theControl).contrlValue);
  287.         
  288.         /* uninvert the title area */
  289.         if(*(**theControl).contrlTitle)
  290.             InvertRect(&invert);
  291.         
  292.         /* if something was chosen from the menu we have to fix all the
  293.             values, easiest way is to erase the control and change it's
  294.             value, that way the update manager will get it redrawn */
  295.         if(menu & 0xffff0000)
  296.         {
  297.             /* restore the coordinates so that we can properly erase the old
  298.                 control */
  299.             GlobalToLocal((Point *) &rect);
  300.         
  301.             rect.right += 1;
  302.             rect.bottom += 1;
  303.             EraseRect(&rect);
  304.             SetCtlValue(theControl, menu & 0xffff);
  305.         }
  306.     }
  307.     
  308.     /* in any case, tell the control manager we done everything that needs
  309.         don'in */
  310.     return 1;
  311. }
  312.  
  313. /* arg, I'm not sure why this is really necessary but it seems the control
  314.     manager is a little confused.  If we just call the above it all seems to
  315.     work though */
  316. void TrackPopUp(varcode, theControl, param)
  317. int                    varcode;
  318. ControlHandle        theControl;
  319. long                param;
  320. {
  321.     DragPopUp(varcode, theControl, param);
  322. }
  323.  
  324. /* figger out what area of the screen the selectable portion of the control
  325.     occupies.  This is one menu line high, the width of the appropriate menu
  326.     item, and possibly offset to right to center the item. */
  327. void CalcPopUpRect(varcode, theControl, pRect)
  328. int                varcode;
  329. ControlHandle    theControl;
  330. Rect            *pRect;
  331. {
  332.     Rect        rect;
  333.     char        item[256];
  334.     MenuHandle    mh;
  335.     
  336.     rect = (**theControl).contrlRect;
  337.  
  338.     /* if we don't have a menu our area is the same as the control's, otherwise
  339.         it's somewhat smaller */
  340.     if(mh = GetMenuHandle(theControl))
  341.     {
  342.         /* get the text that will eventually be drawn */
  343.         GetItem((**theControl).contrlData, (**theControl).contrlValue, item);
  344.     
  345.         /* if there's a title that move the left over the width of the title
  346.             plus a little fudge */
  347.         if(*(**theControl).contrlTitle)
  348.             rect.left += StringWidth((**theControl).contrlTitle) + HSeparation;
  349.             
  350.         /* set the width of the region to the width of the item to be drawn plus fudge */
  351.         CalcMenuSize(mh);    /* do a recalc to get the proper menuWidth */
  352.         rect.right = rect.left + (*mh)->menuWidth /*StringWidth(item) + HSpace * 2*/;
  353.         
  354.         /* the top is the bottom minus fudge minus the height of an item.  We
  355.             could have done this from the top I suppose... */
  356.         rect.top = rect.bottom - VSpace - GetFontHeight();
  357.  
  358.         /* if the item should be centered than offset it by an appropriate amount */
  359.         if(varcode)
  360.             OffsetRect(&rect, ((**theControl).contrlRect.right - (**theControl).contrlRect.left
  361.                                   + rect.left - rect.right) / 2, 0);
  362.  
  363.         /* store the result in the callers cubby hole */
  364.         *pRect = rect;
  365.     }
  366.     else
  367.         SetRect(pRect, rect.top, rect.left, rect.top, rect.left);
  368. }
  369.  
  370. /* find the menu handle for our menu.  It's either in the data field of the control
  371.     or can be found by searching for a menu of the correct menu id in the menu bar. */
  372. MenuHandle GetMenuHandle(theControl)
  373. ControlHandle        theControl;
  374. {
  375.     MenuHandle        mh;
  376.     
  377.     if(!(mh = (**theControl).contrlData))
  378.         mh = GetMHandle((**theControl).contrlRfCon);
  379.     return mh;
  380. }
  381.  
  382. /* figure out how high a line is in the current font */
  383. GetFontHeight()
  384. {
  385.     FontInfo        fi;
  386.     
  387.     GetFontInfo(&fi);
  388.     return fi.ascent + fi.leading + fi.descent;
  389. }
  390.  
  391. /* figure out how far above the base line a character should be drawn */
  392. GetFontOffset()
  393. {
  394.     FontInfo        fi;
  395.     
  396.     GetFontInfo(&fi);
  397.     return fi.leading + fi.descent;
  398. }
  399.  
  400.  
  401. /* the primary entry point to the control.  The only purpose here is to switch
  402.     out into the appropriate routine.  Note that two of the routines (HitPopUp
  403.     and DragPopUp) are defined to return values, they return immediately.  All
  404.     the others will return 0 at the bottom just for propriety's sake. */
  405. pascal long main(varcode, theControl, message, param)
  406. short varcode;
  407. ControlHandle theControl;
  408. short message;
  409. long param;
  410. {
  411.     switch(message)
  412.     {
  413.     case drawCntl:        DrawPopUp(varcode, theControl, param);            break;
  414.     case testCntl:         return HitPopUp(varcode, theControl, param);
  415.     case initCntl:         NewPopUp(varcode, theControl, param);            break;
  416.     case calcCRgns:        CalcPopUp(varcode, theControl, param);            break;
  417.     case dragCntl:        return DragPopUp(varcode, theControl, param);
  418.     case dispCntl:        DispPopUp(varcode, theControl, param);            break;
  419.     case autoTrack:        TrackPopUp(varcode, theControl, param);            break;
  420.     }
  421.  
  422.     return 0;
  423. }
  424.